#include <nros/common.h>
#include <nros/port.h>
#include <nros/asm.h>
#include <nros/tty.h>

void set_pos(int pos);

#define VIDEO_TYPE_MDA		0x10	/* Monochrome Text Display	*/
#define VIDEO_TYPE_CGA		0x11	/* CGA Display 			*/
#define VIDEO_TYPE_EGAM		0x20	/* EGA/VGA in Monochrome Mode	*/
#define VIDEO_TYPE_EGAC		0x21	/* EGA/VGA in Color Mode	*/

#define NPAR 16

extern void keyboard_interrupt(void);

static unsigned char	video_type;		/* Type of display being used	*/
static unsigned long	video_num_columns;	/* Number of text columns	*/
static unsigned long	video_size_row;		/* Bytes per row		*/
static unsigned long	video_num_lines;	/* Number of test lines		*/
static unsigned char	video_page;		/* Initial video page		*/
static unsigned long	video_mem_start;	/* Start of video RAM		*/
static unsigned long	video_mem_end;		/* End of video RAM (sort of)	*/
static unsigned short	video_port_reg;		/* Video register select port	*/
static unsigned short	video_port_val;		/* Video register value port	*/
static unsigned short	video_erase_char;	/* Char+Attrib to erase with	*/

static unsigned long	origin;		/* Used for EGA/VGA fast scroll	*/
static unsigned long	scr_end;	/* Used for EGA/VGA fast scroll	*/
static unsigned long	pos;
static unsigned long	x,y;
static unsigned long	top,bottom;
static unsigned long	state=0;
static unsigned long	npar,par[NPAR];
static unsigned long	ques=0;
static unsigned char	attr=0x07;

tty_t tty = {
  {0, 0, 0, {0}},
  {0, 0, 0, {0}}
};

static void sysbeep(void);

/*
 * this is what the terminal answers to a ESC-Z or csi0c
 * query (= vt100 response).
 */
#define RESPONSE "\033[?1;2c"

/* NOTE! gotoxy thinks x==video_num_columns is ok */
static inline void gotoxy(unsigned int new_x,unsigned int new_y)
{
  if (new_x > video_num_columns || new_y >= video_num_lines)
    return;
  x=new_x;
  y=new_y;
  pos=origin + y*video_size_row + (x<<1);
}

static inline void set_origin(void)
{
  cli();
  outb(12, video_port_reg);
  outb(0xff&((origin-video_mem_start)>>9), video_port_val);
  outb(13, video_port_reg);
  outb(0xff&((origin-video_mem_start)>>1), video_port_val);
  sti();
}

static void scrup(void)
{
  if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM) {
    if (!top && bottom == video_num_lines) {
      origin += video_size_row;
      pos += video_size_row;
      scr_end += video_size_row;
      if (scr_end > video_mem_end) {
	asm volatile("cld\n\t"
		     "rep\n\t"
		     "movsl\n\t"
		     "movl video_num_columns,%1\n\t"
		     "rep\n\t"
		     "stosw"
		     ::"a" (video_erase_char),
		      "c" ((video_num_lines-1)*video_num_columns>>1),
		      "D" (video_mem_start),
		      "S" (origin)
		     );
	scr_end -= origin-video_mem_start;
	pos -= origin-video_mem_start;
	origin = video_mem_start;
      } 
      else {
	asm volatile("cld\n\t"
		     "rep\n\t"
		     "stosw"
		     ::"a" (video_erase_char),
		      "c" (video_num_columns),
		      "D" (scr_end-video_size_row)
		     );
      }
      set_origin();
    } 
    else {
      asm volatile("cld\n\t"
		   "rep\n\t"
		   "movsl\n\t"
		   "movl video_num_columns,%%ecx\n\t"
		   "rep\n\t"
		   "stosw"
		   ::"a" (video_erase_char),
		    "c" ((bottom-top-1)*video_num_columns>>1),
		    "D" (origin+video_size_row*top),
		    "S" (origin+video_size_row*(top+1))
		   );
    }
  }
  else {
      asm volatile("cld\n\t"
		   "rep\n\t"
		   "movsl\n\t"
		   "movl video_num_columns,%%ecx\n\t"
		   "rep\n\t"
		   "stosw"
		   ::"a" (video_erase_char),
		    "c" ((bottom-top-1)*video_num_columns>>1),
		    "D" (origin+video_size_row*top),
		    "S" (origin+video_size_row*(top+1))
		   );
    }
}

static void scrdown(void)
{
  if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM) {
    asm volatile("std\n\t"
		 "rep\n\t"
		 "movsl\n\t"
		 "addl $2,%%edi\n\t"	/* %edi has been decremented by 4 */
		 "movl video_num_columns,%%ecx\n\t"
		 "rep\n\t"
		 "stosw"
		 ::"a" (video_erase_char),
		  "c" ((bottom-top-1)*video_num_columns>>1),
		  "D" (origin+video_size_row*bottom-4),
		  "S" (origin+video_size_row*(bottom-1)-4)
		 );
  }
  else {
    asm volatile("std\n\t"
		 "rep\n\t"
		 "movsl\n\t"
		 "addl $2,%%edi\n\t"	/* %edi has been decremented by 4 */
		 "movl video_num_columns,%%ecx\n\t"
		 "rep\n\t"
		 "stosw"
		 ::"a" (video_erase_char),
		  "c" ((bottom-top-1)*video_num_columns>>1),
		  "D" (origin+video_size_row*bottom-4),
		  "S" (origin+video_size_row*(bottom-1)-4)
		 );
  }
}

static void lf(void)
{
  if (y+1<bottom) {
    y++;
    pos += video_size_row;
    return;
  }
  scrup();
}

static void ri(void)
{
  if (y>top) {
    y--;
    pos -= video_size_row;
    return;
  }
  scrdown();
}

static void cr(void)
{
  pos -= x<<1;
  x=0;
}

static void del(void)
{
  if (x) {
    pos -= 2;
    x--;
    *(unsigned short *)pos = video_erase_char;
  }
}

static void csi_J(int par)
{
  long count;
  long start;

  switch (par) {
  case 0:	/* erase from cursor to end of display */
    count = (scr_end-pos)>>1;
    start = pos;
    break;
  case 1:	/* erase from start to cursor */
    count = (pos-origin)>>1;
    start = origin;
    break;
  case 2: /* erase whole display */
    count = video_num_columns * video_num_lines;
    start = origin;
    break;
  default:
    return;
  }
  asm volatile("cld\n\t"
	       "rep\n\t"
	       "stosw\n\t"
	       ::"c" (count),
		"D" (start),"a" (video_erase_char)
	       );
}

static void csi_K(int par)
{
  long count;
  long start;

  switch (par) {
  case 0:	/* erase from cursor to end of line */
    if (x>=video_num_columns)
      return;
    count = video_num_columns-x;
    start = pos;
    break;
  case 1:	/* erase from start of line to cursor */
    start = pos - (x<<1);
    count = (x<video_num_columns)?x:video_num_columns;
    break;
  case 2: /* erase whole line */
    start = pos - (x<<1);
    count = video_num_columns;
    break;
  default:
    return;
  }
  asm volatile("cld\n\t"
	       "rep\n\t"
	       "stosw\n\t"
	       ::"c" (count),
		"D" (start),"a" (video_erase_char)
	       );
}

void csi_m(void)
{
  int i;

  for (i=0;i<=npar;i++)
    switch (par[i]) {
    case 0:attr=0x07;break;
    case 1:attr=0x0f;break;
    case 4:attr=0x0f;break;
    case 7:attr=0x70;break;
    case 27:attr=0x07;break;
    }
}

static inline void set_cursor(void)
{
  cli();
  outb(14, video_port_reg);
  outb(0xff&((pos-video_mem_start)>>9), video_port_val);
  outb(15, video_port_reg);
  outb(0xff&((pos-video_mem_start)>>1), video_port_val);
  sti();
}

static void insert_char(void)
{
  int i=x;
  unsigned short tmp, old = video_erase_char;
  unsigned short * p = (unsigned short *) pos;

  while (i++<video_num_columns) {
    tmp=*p;
    *p=old;
    old=tmp;
    p++;
  }
}

static void insert_line(void)
{
  int oldtop,oldbottom;

  oldtop=top;
  oldbottom=bottom;
  top=y;
  bottom = video_num_lines;
  scrdown();
  top=oldtop;
  bottom=oldbottom;
}

static void delete_char(void)
{
  int i;
  unsigned short * p = (unsigned short *) pos;

  if (x>=video_num_columns)
    return;
  i = x;
  while (++i < video_num_columns) {
    *p = *(p+1);
    p++;
  }
  *p = video_erase_char;
}

static void delete_line(void)
{
  int oldtop,oldbottom;

  oldtop=top;
  oldbottom=bottom;
  top=y;
  bottom = video_num_lines;
  scrup();
  top=oldtop;
  bottom=oldbottom;
}

static void csi_at(unsigned int nr)
{
  if (nr > video_num_columns)
    nr = video_num_columns;
  else if (!nr)
    nr = 1;
  while (nr--)
    insert_char();
}

static void csi_L(unsigned int nr)
{
  if (nr > video_num_lines)
    nr = video_num_lines;
  else if (!nr)
    nr = 1;
  while (nr--)
    insert_line();
}

static void csi_P(unsigned int nr)
{
  if (nr > video_num_columns)
    nr = video_num_columns;
  else if (!nr)
    nr = 1;
  while (nr--)
    delete_char();
}

static void csi_M(unsigned int nr)
{
  if (nr > video_num_lines)
    nr = video_num_lines;
  else if (!nr)
    nr=1;
  while (nr--)
    delete_line();
}

static int saved_x=0;
static int saved_y=0;

static void save_cur(void)
{
  saved_x=x;
  saved_y=y;
}

static void restore_cur(void)
{
  gotoxy(saved_x, saved_y);
}

void con_write(tty_t * tty)
{
  int nr;
  char c;

  nr = CHARS(tty->write_q);
  while (nr--) {
    GETCH(tty->write_q,c);
    switch(state) {
    case 0:
      if (c>31 && c<127) {
	if (x>=video_num_columns) {
	  x -= video_num_columns;
	  pos -= video_size_row;
	  lf();
	}
	asm volatile("movb attr,%%ah\n\t"
		     "movw %%ax,%1\n\t"
		     ::"a" (c),"m" (*(short *)pos)
		     );
	pos += 2;
	x++;
      } else if (c==27)
	state=1;
      else if (c==10 || c==11 || c==12)
	lf();
      else if (c==13)
	cr();
      else if (c==127)
	del();
      else if (c==8) {
	if (x) {
	  x--;
	  pos -= 2;
	}
      } else if (c==9) {
	c=8-(x&7);
	x += c;
	pos += c<<1;
	if (x>video_num_columns) {
	  x -= video_num_columns;
	  pos -= video_size_row;
	  lf();
	}
	c=9;
      } else if (c==7)
	sysbeep();
      break;
    case 1:
      state=0;
      if (c=='[')
	state=2;
      else if (c=='E')
	gotoxy(0,y+1);
      else if (c=='M')
	ri();
      else if (c=='D')
	lf();
      else if (x=='7')
	save_cur();
      else if (x=='8')
	restore_cur();
      break;
    case 2:
      for(npar=0;npar<NPAR;npar++)
	par[npar]=0;
      npar=0;
      state=3;
      if ((ques=(c=='?')))
	break;
    case 3:
      if (c==';' && npar<NPAR-1) {
	npar++;
	break;
      } else if (c>='0' && c<='9') {
	par[npar]=10*par[npar]+c-'0';
	break;
      } else state=4;
    case 4:
      state=0;
      switch(c) {
      case 'G': case '`':
	if (par[0]) par[0]--;
	gotoxy(par[0],y);
	break;
      case 'A':
	if (!par[0]) par[0]++;
	gotoxy(x,y-par[0]);
	break;
      case 'B': case 'e':
	if (!par[0]) par[0]++;
	gotoxy(x,y+par[0]);
	break;
      case 'C': case 'a':
	if (!par[0]) par[0]++;
	gotoxy(x+par[0],y);
	break;
      case 'D':
	if (!par[0]) par[0]++;
	gotoxy(x-par[0],y);
	break;
      case 'E':
	if (!par[0]) par[0]++;
	gotoxy(0,y+par[0]);
	break;
      case 'F':
	if (!par[0]) par[0]++;
	gotoxy(0,y-par[0]);
	break;
      case 'd':
	if (par[0]) par[0]--;
	gotoxy(x,par[0]);
	break;
      case 'H': case 'f':
	if (par[0]) par[0]--;
	if (par[1]) par[1]--;
	gotoxy(par[1],par[0]);
	break;
      case 'J':
	csi_J(par[0]);
	break;
      case 'K':
	csi_K(par[0]);
	break;
      case 'L':
	csi_L(par[0]);
	break;
      case 'M':
	csi_M(par[0]);
	break;
      case 'P':
	csi_P(par[0]);
	break;
      case '@':
	csi_at(par[0]);
	break;
      case 'm':
	csi_m();
	break;
      case 'r':
	if (par[0]) par[0]--;
	if (!par[1]) par[1] = video_num_lines;
	if (par[0] < par[1] &&
	    par[1] <= video_num_lines) {
	  top=par[0];
	  bottom=par[1];
	}
	break;
      case 's':
	save_cur();
	break;
      case 'u':
	restore_cur();
	break;
      }
    }
  }
  set_cursor();
}

/*
 *  void con_init(void);
 *
 * This routine initalizes console interrupts, and does nothing
 * else. If you want the screen to clear, call tty_write with
 * the appropriate escape-sequece.
 *
 * Reads the information preserved by setup.s to determine the current display
 * type and sets everything accordingly.
 */
void con_init(void)
{
  u8 a;
  char *display_desc = NULL;
  char *display_ptr = NULL;

  video_num_columns = 80;
  video_size_row = video_num_columns * 2;
  video_num_lines = 25;
  video_page = 0;
  video_erase_char = 0x0720;
	
  video_mem_start = (KVM_BASE + 0xb8000);
  video_port_reg	= 0x3d4;
  video_port_val	= 0x3d5;
  video_type = VIDEO_TYPE_CGA;
  video_mem_end = (KVM_BASE + 0xba000);
  display_desc = "*CGA";

  /* Let the user known what kind of display driver we are using */
  display_ptr = ((char *)video_mem_start) + video_size_row - 8;
  while (*display_desc) {
    *display_ptr++ = *display_desc++;
    display_ptr++;
  }
	
  /* Initialize the variables used for scrolling (mostly EGA/VGA)	*/
	
  origin	= video_mem_start;
  scr_end	= video_mem_start + video_num_lines * video_size_row;
  top	= 0;
  bottom	= video_num_lines;

  gotoxy(0, 0);
  a=inb(0x61);
  outb(a|0x80,0x61);
  outb(a,0x61);
}

void sysbeepstop(void)
{
  /* disable counter 2 */
  outb(inb(0x61)&0xFC, 0x61);
}

int beepcount = 0;

static void sysbeep(void)
{
  /* enable counter 2 */
  outb(inb(0x61)|3, 0x61);
  /* set command for counter 2, 2 byte write */
  outb(0xB6, 0x43);
  /* send 0x637 for 750 HZ */
  outb(0x37, 0x42);
  outb(0x06, 0x42);
  /* 1/8 second */
  beepcount = 750/8;
}
